package com.example.demo.handler;

import cn.hutool.core.io.FileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.example.demo.ExampleApplication;
import com.example.demo.handler.excel.Company;
import com.example.demo.handler.excel.CompanyParser;
import com.example.demo.hotload.SpringUtil;
import com.example.demo.hotload.compiler.JavaStringCompiler;
import com.example.demo.hotload.db.HibernateUtil;
import com.example.demo.hotload.spring.ClassLoadUtil;
import com.example.demo.hotload.spring.SpringContextUtil;
import com.example.demo.model.mall.Goods;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.google.gson.stream.JsonReader;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.filters.ExpiresFilter;
import org.apache.commons.codec.digest.Md5Crypt;
import org.hibernate.Session;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import xyz.erupt.annotation.SceneEnum;
import xyz.erupt.annotation.fun.AttachmentProxy;
import xyz.erupt.annotation.fun.PowerObject;
import xyz.erupt.core.config.GsonFactory;
import xyz.erupt.core.constant.EruptRestPath;
import xyz.erupt.core.controller.EruptDataController;
import xyz.erupt.core.controller.EruptFileController;
import xyz.erupt.core.controller.EruptModifyController;
import xyz.erupt.core.controller.EruptTabController;
import xyz.erupt.core.invoke.DataProcessorManager;
import xyz.erupt.core.invoke.DataProxyInvoke;
import xyz.erupt.core.prop.EruptProp;
import xyz.erupt.core.service.EruptCoreService;
import xyz.erupt.core.service.EruptService;
import xyz.erupt.core.util.*;
import xyz.erupt.core.view.EruptApiModel;
import xyz.erupt.core.view.EruptModel;
import xyz.erupt.jpa.dao.EruptDao;
import xyz.erupt.jpa.model.BaseModel;
import xyz.erupt.monitor.vo.Sys;
import xyz.erupt.tpl.controller.EruptTplController;
import xyz.erupt.tpl.service.EruptTplService;
import xyz.erupt.upms.model.EruptMenu;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.util.*;

@Slf4j
@Component
public class UploadProxy implements AttachmentProxy {

    private String path;

    @Autowired
    private EruptTabController tabController;

    @Autowired
    private EruptTplController tplController;

    @Autowired
    private EruptModifyController modifyController;

    @Autowired
    private EruptProp eruptProp;

    @Autowired
    private EruptService eruptService;

    private JavaStringCompiler compiler;

    @SneakyThrows
    @Override
    public String upLoad(InputStream inputStream, String path) {
        log.info("上传文件 " + path);
        this.path = path;
        CompanyParser parser = new CompanyParser();
        EasyExcel.read(inputStream, parser).doReadAll();
        Company company = parser.getCompany();
        String fileName = path.substring(path.lastIndexOf("/") + 1);
        company.name = fileName.substring(0,fileName.lastIndexOf("."));
        // 新增企业菜单
         String newMenu = "{\n" +
                 "    \"name\": \"" + company.name + "\",\n" +
                 "    \"status\": \"1\",\n" +
                 "    \"parentMenu\": {\n" +
                 "        \"id\": \"98\",\n" +
                 "        \"name\": \"企业菜单\"\n" +
                 "    },\n" +
                 "    \"sort\": 1\n" +
                 "}";
         // TODO 添加企业菜单
        long companyId = 1000;
        EruptMenu eruptMenu = (EruptMenu) modifyAdd("EruptMenu", new Gson().fromJson(newMenu, JsonObject.class));
        companyId = eruptMenu.getId();
        // 新增字段
        long stamp = System.currentTimeMillis();
        for (Company.Menu menu : company.menus) {
            // FieldName
            // showName
            for (Company.Column column : menu.columns) {
                stamp++;
                column.fieldName = ("F_" + (MD5Util.digest(column.name) + "_" + stamp));
                String columnJson = "{\"fieldName\":\""+ column.fieldName + "\",\"showName\":\""+column+"\",\"sort\":1,\"type\":\"INPUT\",\"query\":true,\"sortable\":false,\"notNull\":true,\"isShow\":true}";
                // 属性不需要id
                tabController.addTabEruptData("GeneratorClass", "fields", new Gson().fromJson(columnJson, JsonObject.class));
            }
        }

        // 新增class配置
        stamp = System.currentTimeMillis();
        for (Company.Menu menu : company.menus) {
            String className = "M_" + MD5Util.digest(company.name + "_" + menu) + "_" + stamp;
            String tableName = "T_" + (MD5Util.digest(menu.name) + "_" + stamp);
            String classJson = "{\"name\":\"" + company.name + "_" + menu.name + "\",\"className\":\"" + className + "\",\"tableName\":\"" + tableName + "\",\"fields\":[TODO]}";
            StringBuilder fieldJson = new StringBuilder();
            String fields = "";
            for (Company.Column column : menu.columns) {
                // 拼接fields属性
                String field = "{\"fieldName\":\""+column.fieldName+"\",\"showName\":\""+column.name+"\",\"sort\":1,\"type\":\"INPUT\",\"linkClass\":null,\"query\":true,\"sortable\":false,\"notNull\":true,\"isShow\":true,\"id\":-480}";
                fieldJson.append(field).append(",");
            }

            if (fieldJson.length() > 0) {
                fields = fieldJson.substring(0, fieldJson.length() - 1);
            }

            if (!fields.isEmpty()) {
                classJson =  classJson.replace("TODO", fields);
            }
            BaseModel obj = (BaseModel) modifyAdd("GeneratorClass", new Gson().fromJson(classJson, JsonObject.class));
            SourceHttpResponse sourceHttpResponse = new SourceHttpResponse();
            tplController.operationTpl("GeneratorClass", "632397762", new String[]{obj.getId().toString()}, sourceHttpResponse);

            Document document = Jsoup.parse(sourceHttpResponse.getByteArrayOutputStream().toString());
            Elements elements = document.getElementsByClass("language-java");
            if (!elements.isEmpty()) {
                String text = elements.get(0).text();
//                File sourceDest = new File("src/main/java/com/example/demo/sample");
//                if (!sourceDest.exists()) {
//                    sourceDest.mkdirs();
//                }
//
//                File destFile = new File(sourceDest, className + ".java");
//                if (destFile.exists()) {
//                    destFile.delete();
//                }
//                destFile.createNewFile();
                String packageName = "com.example.demo.sample";
//                FileOutputStream fileOutputStream = new FileOutputStream(destFile);
                OutputStream outputStream = new ByteArrayOutputStream();
                StringBuilder sb = new StringBuilder("package "+packageName+";\n\n");
                sb.append(text);
                outputStream.write(sb.toString().getBytes());
                outputStream.flush();
                outputStream.close();

                // TODO: 2024/1/23 拿到java源文件进行编译，加载
                compiler = new JavaStringCompiler();
                Map<String, byte[]> classes = compiler.compile(className + ".java", sb.toString());
                Class<?> clazz = compiler.loadClass(packageName + "." + className, classes);
                // 将byte数据存入classpath中
                String classpath = compiler.getClass().getResource("/").getPath();
//                String classpath = "/Users/zhangdengjie/develop/erupt";
                File classFile = new File( classpath + "com/example/demo/sample",className + ".class");
                FileUtil.writeBytes(classes.get(packageName + "." + className), classFile);
                // 加载到spring classloader中
//                URLClassLoader urlClassLoader = (URLClassLoader) EruptSpringUtil.getApplicationContext().getClassLoader();
//                Method method = ReflectionUtils.findMethod(URLClassLoader.class, "addURL", URL.class);
//                method.setAccessible(true);
//                ReflectionUtils.invokeMethod(method, urlClassLoader, classFile.getParentFile().getCanonicalFile().toURI().toURL());
//                Class<?> clazz = EruptSpringUtil.getApplicationContext().getClassLoader().loadClass(className);
//                Class<?> clazz = UploadProxy.class.getClassLoader().loadClass(className);
                //每次都是new新的ClassLoader对象
//                Class<?> clazz = ClassLoadUtil.loadClass(classFile.getAbsolutePath(), className);
//                SpringContextUtil.registerSingleton(clazz);
                log.info(clazz.toGenericString());
                EruptCoreService.registerErupt(clazz);

                SpringUtil.registerClass(clazz);

                hibernateUtil.obtainSessionFactory(clazz,menu.contents);

                // 新增企业菜单
                newMenu = "{\n" +
                        "    \"name\": \"" + menu.name + "\",\n" +
                        "    \"status\": \"1\",\n" +
                        "    \"parentMenu\": {\n" +
                        "        \"id\": \""+companyId+"\",\n" +
                        "        \"name\": \""+company.name+"\"\n" +
                        "    },\n" +
                        "    \"sort\": 1\n" +
                        "}";
                long menuId = 1000;
                EruptMenu functionMenu = (EruptMenu) modifyAdd("EruptMenu", new Gson().fromJson(newMenu, JsonObject.class));
                menuId = functionMenu.getId();
                // 写入完数据，绑定关系
                String bindParam = "{\n" +
                        "    \"name\": \""+menu.name+"\",\n" +
                        "    \"status\": \"1\",\n" +
                        "    \"parentMenu\": {\n" +
                        "        \"id\": "+companyId+",\n" +
                        "        \"name\": \""+company.name+"\"\n" +
                        "    },\n" +
                        "    \"type\": \"table\",\n" +
                        "    \"value\": \""+className+"\",\n" +
                        "    \"sort\": 1,\n" +
                        "    \"code\": \""+functionMenu.getCode()+"\",\n" +
                        "    \"id\": \""+menuId+"\"\n" +
                        "}";
                try {
                    modifyUpdate("EruptMenu", new Gson().fromJson(bindParam, JsonObject.class));
                } catch (Exception e) {
                    log.error(e.getMessage());
                }
                reboot();
            }
        }
        return path;
    }

    private void reboot() {
        Thread restartThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    ExampleApplication.stop();
                } catch (InterruptedException ignored) {
                }
            }
        });
        restartThread.setDaemon(false);
        restartThread.start();
    }

    @Autowired
    private HibernateUtil hibernateUtil;

    @Autowired
    private EruptDao eruptDao;

    /**
     * 拿出来实现是因为可以轻易获取id
     * @param erupt
     * @param data
     * @return
     */
    @SneakyThrows
    private Object modifyAdd(String erupt, JsonObject data) {
        EruptModel eruptModel = EruptCoreService.getErupt(erupt);
        Erupts.powerLegal(eruptModel, PowerObject::isAdd);
        Map<String, Object> extraData = new HashMap<>();
        Object obj = EruptUtil.jsonToEruptEntity(eruptModel, data, extraData);
        DataProxyInvoke.invoke(eruptModel, (dataProxy -> dataProxy.beforeAdd(obj)));
        DataProcessorManager.getEruptDataProcessor(eruptModel.getClazz()).addData(eruptModel, obj);
        this.modifyLog(eruptModel, "ADD", data.toString());
        DataProxyInvoke.invoke(eruptModel, (dataProxy -> dataProxy.afterAdd(obj)));
        return obj;
    }

    @SneakyThrows
    private Object modifyUpdate(String erupt, JsonObject data) {
        EruptModel eruptModel = EruptCoreService.getErupt(erupt);
        Erupts.powerLegal(eruptModel, PowerObject::isEdit);
        EruptApiModel eruptApiModel = EruptUtil.validateEruptValue(eruptModel, data);
        if (eruptApiModel.getStatus() == EruptApiModel.Status.ERROR) return eruptApiModel;
        eruptService.verifyIdPermissions(eruptModel, data.get(eruptModel.getErupt().primaryKeyCol()).getAsString());
        Object o = GsonFactory.getGson().fromJson(data.toString(), eruptModel.getClazz());
        EruptUtil.clearObjectDefaultValueByJson(o, data);
        Object obj = EruptUtil.dataTarget(eruptModel, o, DataProcessorManager.getEruptDataProcessor(eruptModel.getClazz())
                .findDataById(eruptModel, ReflectUtil.findClassField(eruptModel.getClazz(), eruptModel.getErupt().primaryKeyCol()).get(o)), SceneEnum.EDIT);
        DataProxyInvoke.invoke(eruptModel, (dataProxy -> dataProxy.beforeUpdate(obj)));
        DataProcessorManager.getEruptDataProcessor(eruptModel.getClazz()).editData(eruptModel, obj);
        this.modifyLog(eruptModel, "EDIT", data.toString());
        DataProxyInvoke.invoke(eruptModel, (dataProxy -> dataProxy.afterUpdate(obj)));
        return EruptApiModel.successApi();
    }

    @Override
    public String fileDomain() {
        return EruptRestPath.ERUPT_ATTACHMENT;
    }

    @Override
    public boolean isLocalSave() {
        return !path.endsWith("xlsx");
    }

    public static class CompanyJson {
        private String name;
        private List<CompanyFunction> functions;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public List<CompanyFunction> getFunctions() {
            return functions;
        }

        public void setFunctions(List<CompanyFunction> functions) {
            this.functions = functions;
        }
    }

    public static class CompanyFunction {
        private String name;
        private List<CompanyColumn> columns;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public List<CompanyColumn> getColumns() {
            return columns;
        }

        public void setColumns(List<CompanyColumn> columns) {
            this.columns = columns;
        }
    }

    public static class CompanyColumn {
        private String name;
        @Expose(deserialize = false)
        private String fieldName;

        public String getFieldName() {
            return fieldName;
        }

        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    private void modifyLog(EruptModel eruptModel, String placeholder, String content) {
        log.info("[" + eruptModel.getEruptName() + " -> " + placeholder + "]:" + content);
    }
}
